import os
import csv
import struct
import sys

from PyQt5.QtCore import QPointF, pyqtSignal, QThread, QObject, QEvent
from PyQt5.QtGui import QResizeEvent
from PyQt5.QtWidgets import QMainWindow, QFileDialog

from utils.csi_detail import getDetail, CSI_Detail, CSI_Seq
from utils.labelsample.open_thread import OpenThread
from utils.labelsample.ui.sampleviewer import SampleViewer
from utils.labelsample.ui_gen.mainwindow_impl import Ui_MainWindow

from utils.csi_read import read_from_csifile
from utils.lbl_data import LblData


class MainWindow(QMainWindow, Ui_MainWindow):
    optionChanged = pyqtSignal(int, int, int, int, int)
    titleprefix = '标记工具 - '

    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)
        self.setupUi(self)
        self.sampleViewer = SampleViewer(self)
        self.sampleViewer.setMainWindow(self)
        self.optionChanged.connect(self.sampleViewer.onMainWindowOptionChanged)
        self.sampleViewer.xscalechanged.connect(self.onSampleViewerXScaleChanged)
        self.cbDataType.activated.connect(self.onCbDataTypeActivated)
        self.sbSendAntenna.valueChanged.connect(self.notifyoptionChanged)
        self.sbCSINum.valueChanged.connect(self.notifyoptionChanged)
        self.sbAntenna.valueChanged.connect(self.notifyoptionChanged)
        self.sbAntenna1.valueChanged.connect(self.notifyoptionChanged)
        self.sbAntenna2.valueChanged.connect(self.notifyoptionChanged)
        self.setDataType(0)
        # don't show horizontalscrollbar
        self.sampleViewer.setFixedHeight(
            self.scrollArea.height() - self.scrollArea.horizontalScrollBar().height() + 7)
        self.scrollArea.horizontalScrollBar().valueChanged.connect(self.onScrollAreaHorScrollBarValueChanged)
        self.sampleViewer.adaptWidth(self.scrollArea.width())
        self.scrollArea.installEventFilter(self)
        self.scrollArea.setWidget(self.sampleViewer)
        self.sampleViewer.setHolder(self.scrollArea)
        self.actionOpenCSI.triggered.connect(self.onActionOpenCSITriggered)
        self.optionChanged.emit(0, self.sbCSINum.value(), self.sbSendAntenna.value(), self.sbAntenna.value(),
                                self.sbAntenna.value())
        self.cbFixYlim.stateChanged.connect(self.freshSampleViewer)
        self.dsbYlimmin.valueChanged.connect(self.freshSampleViewer)
        self.dsbYlimmax.valueChanged.connect(self.freshSampleViewer)
        self.btnGenerate.clicked.connect(self.btngenerate_clicked)
        # self.test()

    def freshSampleViewer(self):
        self.sampleViewer.update()

    def onCbDataTypeActivated(self, n):
        self.setDataType(n)
        self.notifyoptionChanged()

    def notifyoptionChanged(self):
        n = self.cbDataType.currentIndex()
        if n == 0 or n == 1:
            self.optionChanged.emit(n, self.sbCSINum.value(), self.sbSendAntenna.value(),
                                    self.sbAntenna.value(),
                                    self.sbAntenna.value())
        elif n == 2 or n == 3:
            self.optionChanged.emit(n, self.sbCSINum.value(), self.sbSendAntenna.value(),
                                    self.sbAntenna1.value(),
                                    self.sbAntenna2.value())

    def setDataType(self, n):
        if n == 0 or n == 1:
            self.frameAntenna.setVisible(True)
            self.frameAntenna1.setVisible(False)
            self.frameAntenna2.setVisible(False)
        elif n == 2 or n == 3:
            self.frameAntenna.setVisible(False)
            self.frameAntenna1.setVisible(True)
            self.frameAntenna2.setVisible(True)

    def onSampleViewerXScaleChanged(self, demo, point: QPointF, d):
        if not demo:
            viewer = self.sampleViewer
            tmp = point.x() * d / viewer.width() - point.x()
            v = self.scrollArea.horizontalScrollBar().value()
            viewer.adaptWidth(self.scrollArea.width())
            offset = v + tmp - d + viewer.width()
            self.scrollArea.horizontalScrollBar().setValue(offset)

    def onScrollAreaHorScrollBarValueChanged(self, value):
        self.sampleViewer.setyoffset(value)

    def onActionOpenCSITriggered(self):
        dir = os.path.join(os.getcwd(), '../../csifiles')
        if not os.path.exists(dir):
            os.mkdir(dir)
        path = QFileDialog.getOpenFileName(self, '打开CSI文件', dir, 'CSI文件(*.csi);;CSV文件(*.csv)')[0]  # type:str
        if path != '':
            if path.endswith('csi'):
                self.csilbldatname = os.path.splitext(path)[0] + '.lbl'
                if not os.path.exists(self.csilbldatname):
                    self.openthread = OpenThread(path)
                    self.openthread.opencomplete.connect(self.onOpenComplete)
                    self.openthread.start()
                    self.centralWidget().setEnabled(False)
                    self.menuBar().setEnabled(False)
                    self.statusBar().showMessage('检测到新数据，正在生成csi标记数据')
                else:
                    self.statusBar().showMessage('读取csi标记数据')
                    detail = self.readDetail(self.csilbldatname)
                    self.sampleViewer.setCSIDetail(detail)
                    self.statusBar().showMessage('')
            elif path.endswith('csv'):
                self.openCSVFile(path)
            self.setWindowTitle(self.titleprefix + path)

    def onOpenComplete(self):
        detail = self.openthread.detail
        self.statusBar().showMessage('正在写入csi标记数据到磁盘')
        self.saveDetail(detail)
        self.sampleViewer.setCSIDetail(detail)
        self.centralWidget().setEnabled(True)
        self.menuBar().setEnabled(True)

    def saveDetail(self, detail):
        with open(self.csilbldatname, 'wb') as f:
            tmp = struct.pack('<id', detail.totalCnt, detail.totalTime)
            f.write(tmp)
            for i in range(detail.totalCnt):
                tmp = struct.pack('<d', detail.times[i])
                f.write(tmp)
            l1 = len(detail.amplitude)
            tmp = struct.pack('<i', l1)
            f.write(tmp)
            for i in range(l1):
                l2 = len(detail.amplitude[i])
                tmp = struct.pack('<i', l2)
                f.write(tmp)
                for j in range(l2):
                    l3 = len(detail.amplitude[i][j])
                    tmp = struct.pack('<i', l3)
                    f.write(tmp)
                    for k in range(l3):
                        l4 = len(detail.amplitude[i][j][k])
                        tmp = struct.pack('<i', l4)
                        f.write(tmp)
                        for m in range(l4):
                            tmp = struct.pack('<dd', detail.amplitude[i][j][k][m].time,
                                              detail.amplitude[i][j][k][m].value)
                            f.write(tmp)

            l1 = len(detail.amplitudedif)
            tmp = struct.pack('<i', l1)
            f.write(tmp)
            for i in range(l1):
                l2 = len(detail.amplitudedif[i])
                tmp = struct.pack('<i', l2)
                f.write(tmp)
                for j in range(l2):
                    l3 = len(detail.amplitudedif[i][j])
                    tmp = struct.pack('<i', l3)
                    f.write(tmp)
                    for k in range(l3):
                        l4 = len(detail.amplitudedif[i][j][k])
                        tmp = struct.pack('<i', l4)
                        f.write(tmp)
                        for m in range(l4):
                            l5 = len(detail.amplitudedif[i][j][k][m])
                            tmp = struct.pack('<i', l5)
                            f.write(tmp)
                            for n in range(l5):
                                tmp = struct.pack('dd', detail.amplitudedif[i][j][k][m][n].time,
                                                  detail.amplitudedif[i][j][k][m][n].value)
                                f.write(tmp)

            l1 = len(detail.phase)
            tmp = struct.pack('<i', l1)
            f.write(tmp)
            for i in range(l1):
                l2 = len(detail.phase[i])
                tmp = struct.pack('<i', l2)
                f.write(tmp)
                for j in range(l2):
                    l3 = len(detail.phase[i][j])
                    tmp = struct.pack('<i', l3)
                    f.write(tmp)
                    for k in range(l3):
                        l4 = len(detail.phase[i][j][k])
                        tmp = struct.pack('<i', l4)
                        f.write(tmp)
                        for m in range(l4):
                            tmp = struct.pack('<dd', detail.phase[i][j][k][m].time,
                                              detail.phase[i][j][k][m].value)
                            f.write(tmp)

            l1 = len(detail.phasedif)
            tmp = struct.pack('<i', l1)
            f.write(tmp)
            for i in range(l1):
                l2 = len(detail.phasedif[i])
                tmp = struct.pack('<i', l2)
                f.write(tmp)
                for j in range(l2):
                    l3 = len(detail.phasedif[i][j])
                    tmp = struct.pack('<i', l3)
                    f.write(tmp)
                    for k in range(l3):
                        l4 = len(detail.phasedif[i][j][k])
                        tmp = struct.pack('<i', l4)
                        f.write(tmp)
                        for m in range(l4):
                            l5 = len(detail.phasedif[i][j][k][m])
                            tmp = struct.pack('<i', l5)
                            f.write(tmp)
                            for n in range(l5):
                                tmp = struct.pack('dd', detail.phasedif[i][j][k][m][n].time,
                                                  detail.phasedif[i][j][k][m][n].value)
                                f.write(tmp)

    def readDetail(self, file) -> CSI_Detail:
        ret = CSI_Detail()
        with open(file, 'rb') as f:
            tmp = f.read(12)
            cnt, totaltime = struct.unpack('<id', tmp)
            ret.totalCnt = cnt
            ret.totalTime = totaltime
            for i in range(cnt):
                tmp = f.read(8)
                time, = struct.unpack('<d', tmp)
                ret.times.append(time)
            tmp = f.read(4)
            tmp1, = struct.unpack('<i', tmp)
            for i in range(tmp1):
                ret.amplitude.append([])
                tmp = f.read(4)
                tmp2, = struct.unpack('<i', tmp)
                for j in range(tmp2):
                    ret.amplitude[i].append([])
                    tmp = f.read(4)
                    tmp3, = struct.unpack('<i', tmp)
                    for k in range(tmp3):
                        ret.amplitude[i][j].append([])
                        tmp = f.read(4)
                        tmp4, = struct.unpack('<i', tmp)
                        for m in range(tmp4):
                            tmp = f.read(16)
                            time, value = struct.unpack('<dd', tmp)
                            ret.amplitude[i][j][k].append(CSI_Seq(value, time))

            tmp = f.read(4)
            tmp1, = struct.unpack('<i', tmp)
            for i in range(tmp1):
                ret.amplitudedif.append([])
                tmp = f.read(4)
                tmp2, = struct.unpack('<i', tmp)
                for j in range(tmp2):
                    ret.amplitudedif[i].append([])
                    tmp = f.read(4)
                    tmp3, = struct.unpack('<i', tmp)
                    for k in range(tmp3):
                        ret.amplitudedif[i][j].append([])
                        tmp = f.read(4)
                        tmp4, = struct.unpack('<i', tmp)
                        for m in range(tmp4):
                            ret.amplitudedif[i][j][k].append([])
                            tmp = f.read(4)
                            tmp5, = struct.unpack('<i', tmp)
                            for n in range(tmp5):
                                tmp = f.read(16)
                                time, value = struct.unpack('<dd', tmp)
                                ret.amplitudedif[i][j][k][m].append(CSI_Seq(value, time))
            tmp = f.read(4)
            tmp1, = struct.unpack('<i', tmp)
            for i in range(tmp1):
                ret.phase.append([])
                tmp = f.read(4)
                tmp2, = struct.unpack('<i', tmp)
                for j in range(tmp2):
                    ret.phase[i].append([])
                    tmp = f.read(4)
                    tmp3, = struct.unpack('<i', tmp)
                    for k in range(tmp3):
                        ret.phase[i][j].append([])
                        tmp = f.read(4)
                        tmp4, = struct.unpack('<i', tmp)
                        for m in range(tmp4):
                            tmp = f.read(16)
                            time, value = struct.unpack('<dd', tmp)
                            ret.phase[i][j][k].append(CSI_Seq(value, time))
            tmp = f.read(4)
            tmp1, = struct.unpack('<i', tmp)
            for i in range(tmp1):
                ret.phasedif.append([])
                tmp = f.read(4)
                tmp2, = struct.unpack('<i', tmp)
                for j in range(tmp2):
                    ret.phasedif[i].append([])
                    tmp = f.read(4)
                    tmp3, = struct.unpack('<i', tmp)
                    for k in range(tmp3):
                        ret.phasedif[i][j].append([])
                        tmp = f.read(4)
                        tmp4, = struct.unpack('<i', tmp)
                        for m in range(tmp4):
                            ret.phasedif[i][j][k].append([])
                            tmp = f.read(4)
                            tmp5, = struct.unpack('<i', tmp)
                            for n in range(tmp5):
                                tmp = f.read(16)
                                time, value = struct.unpack('<dd', tmp)
                                ret.phasedif[i][j][k][m].append(CSI_Seq(value, time))
        return ret

    def test(self):
        self.csilbldatname = 'D:\\test.lbl'
        detail = CSI_Detail()
        self.saveDetail(detail)

    def eventFilter(self, obj: QObject, event: QEvent):
        if obj == self.scrollArea and event.type() == QEvent.Resize:
            self.sampleViewer.setFixedHeight(
                self.scrollArea.height() - self.scrollArea.horizontalScrollBar().height() - 7)

            self.sampleViewer.adaptWidth(self.scrollArea.width())
            return True
        return QMainWindow.eventFilter(self, obj, event)

    def btngenerate_clicked(self):
        if not self.sampleViewer.demo:
            self.statusBar().showMessage('正在写入label.csv')
            regions = self.sampleViewer.regions

            cnt = self.sampleViewer.csi_detail.totalCnt
            times = self.sampleViewer.csi_detail.times
            path = os.path.splitext(self.csilbldatname)[0] + '_label.csv'
            dat = [-1] * cnt
            for i in range(len(regions)):
                region = regions[i]  # type:LblData
                type = region.type
                x1 = -1
                x2 = -1
                for j in range(cnt):
                    if times[j] > region.startTime:
                        x1 = j
                        break
                for j in range(cnt - 1, -1, -1):
                    if times[j] < region.endTime:
                        x2 = j
                        break
                if x1 < 0: x1 = 0
                if x2 > cnt - 1: x2 = cnt - 1
                for j in range(x1, x2 + 1):
                    dat[j] = type

            with open(path, 'w', newline='') as f:
                writer = csv.writer(f)
                for i in range(cnt):
                    writer.writerow((i, dat[i]))

            self.statusBar().showMessage('写入成功')
